function Element(symbol, name, number, weight, r) {
  this.symbol = symbol;
  this.name = name;
  this.number = number;
  this.weight = weight;
  if      (number <= 2)  { this.period = 1; this.group = number == 1 ? number : 18; }
  else if (number <= 10) { this.period = 2; this.group = number + (number <= 4 ? -2 : 8); }
  else if (number <= 18) { this.period = 3; this.group = number + (number <= 12 ? -10 : 0); }
  else if (number <= 36) { this.period = 4; this.group = number - 18; }
  else if (number <= 54) { this.period = 5; this.group = number - 36; }
  else if (number <= 86) { this.period = 6; this.group = number - (number <= 56 ? -54 : -68); }
  this.neutrons = Math.round(weight) - number;
  this.valence = this.group - (this.group < 3 ? 0 : (this.period == 1 ? 16 : 10));
  this.removed = r != null ? r : false;
}

Element.prototype = {
  getColumn : function() {
    return this.group - (this.group < 3 ? 1 : 15);
  },
  getRow : function() {
    return this.period - 1;
  }
};

var s2e = {
  "?"  : new Element("", "", 0, 0, false),

  "H"  : new Element("H",  "Hydrogen",  1,  1.00794,  false),
  "He" : new Element("He", "Helium",    2,  4.002602, false),

  "Li" : new Element("Li", "Lithium",   3,  6.941,    false),
  "Be" : new Element("Be", "Beryllium", 4,  9.012182, false),
  "B"  : new Element("B",  "Boron",     5,  10.811,   false),
  "C"  : new Element("C",  "Carbon",    6,  12.0107,  false),
  "N"  : new Element("N",  "Nitrogen",  7,  14.0067,  false),
  "O"  : new Element("O",  "Oxygen",    8,  15.9994,  false),
  "F"  : new Element("F",  "Fluorine",  9,  18.998,   false),
  "Ne" : new Element("Ne", "Neon",      10, 20.180,   false),

  "Na" : new Element("Na", "Sodium",    11, 22.98976, false),
  "Mg" : new Element("Mg", "Magnesium", 12, 24.305,   false),
  "Al" : new Element("Al", "Aluminum",  13, 26.98154, false),
  "Si" : new Element("Si", "Silicon",   14, 28.0855,  false),
  "P"  : new Element("P", "Phosphorus", 15, 30.973762,false),
  "S"  : new Element("S",  "Sulfur",    16, 32.065,   false),
  "Cl" : new Element("Cl", "Chlorine",  17, 35.453,   false),
  "Ar" : new Element("Ar", "Argon",     18, 39.948,   false),

  "K"  : new Element("K", "Potassium", 19, 39.098, false),
  "Ca" : new Element("Ca", "Calcium",  20, 40.08,  false),
  "Br" : new Element("Br", "Bromine",  35, 79.904, false),
  "Kr" : new Element("Kr", "Krypton",  36, 83.798, false),

  "Rb" : new Element("Rb", "Rubidium",  37, 85.467, false),
  "Sr" : new Element("Sr", "Strontium", 38, 87.62,  false),
  "I"  : new Element("I",  "Iodine",    53, 126.905, false),
  "Xe" : new Element("Xe", "Xenon",     54, 131.29,  false),

  "Cs" : new Element("Cs", "Cesium",    55, 132.90, false),
  "Ba" : new Element("Ba", "Barium",    56, 137.33, false),
  "At" : new Element("At", "Astatine",  85, 210,    false),
  "Rn" : new Element("Rn", "Radon",     86, 222,    false)
};

//  atomic weight to nucleus coordinates
var aw2nc = [ [],  //  0
  [ 0.00,  0.00],  //  1
  // 2
  [-0.08, -0.08,
    0.08,  0.08],
  //  3
  [-0.11,  0.08,
   -0.02, -0.13,
    0.11,  0.08],

  [-0.12,  0.07,
    0.12, -0.07,
   -0.08, -0.11,
    0.08,  0.11],  //  4

  [-0.12,  0.08,
    0.12, -0.08,
   -0.12, -0.16,
    0.12,  0.16,
   -0.04, -0.04],  //  5
  [-0.07,  0.12,  0.15, -0.04, -0.10, -0.14,  0.10,  0.14,  0.07, -0.12, -0.15,  0.04],  //  6
  [-0.08,  0.14,  0.18, -0.06, -0.08, -0.16,  0.14,  0.14,  0.10, -0.14, -0.20,  0.02, -0.02,  0.0]  //  7
];

for (var i = aw2nc.length; i < 16; i++) {
  aw2nc.push(new NucleusCoordinates(i));
}

function NucleusCoordinates(weight) {
  var coordinates = [];
  var ar = 0.09 * Math.pow(weight, 0.333); // atomic radius
  var a = 0;
  for (var i = 0; i < weight; i++) {
    var p = Math.PI * i / (2 * weight) ;  //  polar angle
    var r = 0.064 + ar * Math.cos(p);    //  distance from origin in 2 dimensions
    a += 0.09 * Math.PI / Math.pow(r, 0.76);             //  azimuthal angle
    coordinates.push(r * Math.sin(a));    //  x coordinate
    coordinates.push(r * Math.cos(a));    //  y coordinate
  }
  return coordinates;
}

//  electron coordinates
var ec = [ 0.56, 0.24, 0.24, -0.56,
  1.00, -0.48, 0.48, 1.00, -1.00, 0.48, -0.48, -1.00,
  0.78, -0.78, 0.78, 0.78, -0.78, 0.78, -0.78, -0.78 ];


function Atom(symbol) {
  this.symbol = symbol;
  if (symbol == "?") {
    this.protons = 0;
    this.neutrons = 0;
    return;
  }
  var e = s2e[symbol];
  this.name = e.name;
  this.protons = e.number;
  this.neutrons = Math.round(e.weight - e.number);
}

Atom.prototype = {
  drawAtom : function (ctx, x, y, scale) {
    this.drawAtomicModel(ctx, x, y, 0.3 * scale);
    var r = 0.4 * scale;
    var ac; // atom color
    ctx.lineWidth = 0.08 * scale;
    switch(this.symbol) {
      case "H"  : ac = { r : 100, g : 216, b : 255 }; r = 0.24 * scale; break; // light brilliant bluish cyan
      case "He" : ac = { r : 255, g : 216, b : 100 }; r = 0.24 * scale; break; // light brilliant amber
      case "O"  : ac = { r : 255, g : 100, b : 139 }; break; // light brilliant crimson
      case "C"  : ac = { r : 128, g : 128, b : 100 }; break; // flat gray
      case "Na" : ac = { r : 216, g : 255, b : 100 }; r = 0.54 * scale; break; // light brilliant cyan
      case "Cl" : ac = { r : 139, g : 100, b : 255 }; r = 0.54 * scale; break; // light brilliant indigo
      case "?"  : ac = { r : 224, g : 224, b : 224 }; break;
    }
    ac.toString = function() {
      return "rgba(" + ac.r + "," + ac.g + "," + ac.b + ",1)";
    };

    var add = 192;
    var divide = 2;
    var startColor = "rgba(" + Math.round((ac.r + add / divide)) + "," +
      Math.round((ac.g + add / divide)) + "," + Math.round((ac.b + add / divide)) + ",0.7)";

    var radgrad = ctx.createRadialGradient(x - 0.08 * scale, y - 0.08 * scale, 0.08 * scale, x, y, 0.64 * scale);
    radgrad.addColorStop(0, startColor);
    radgrad.addColorStop(1, ac.toString());

    ctx.fillStyle = radgrad;
    ctx.fillCircle(x, y, r);
    ctx.fillStyle = this.symbol == "?" ? "#aaaaaa" : "#444444";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.font = Math.floor(0.32 * scale) + "px sans-serif";
    ctx.fillText(this.symbol, x, y - 0.01 * scale, null);
  },

  drawAtomicModel : function (ctx, x, y, scale, options) {
    this.options = options ? options : { label : false };
    this.drawElectrons(ctx, x, y, scale, options);
    this.drawNucleus(ctx, x, y, scale, options);
  },

  drawConstructModel : function (ctx, x, y, scale) {
    ctx.clearRect(x - 2 * scale, y - 2 * scale, x + 2 * scale, y + 2 * scale);
    var i = construct_score.nucleus;
    var j = construct_score.electrons;
    var s = this.symbol;
    if (0 <= i && i <= 4) {
      if (i == 4) {
        this.drawNucleus(ctx, x, y, scale);
      } else if (s == "He") {
        new Atom("Li").drawNucleus(ctx, x, y, scale);
      } else if (s == "Li") {
        new Atom("He").drawNucleus(ctx, x, y, scale);
      } else {
        this.drawNucleus(ctx, x, y, scale);
      }
    }
    var ee = ""; // equivalent element;
    switch (j) {
      case 0  : ee = "?"; break;
      case 1  : ee = "H"; break;
      case 2  : ee = "He"; break;
      case 3  : ee = "Li"; break;
      case 4  : ee = "Be"; break;
      case 5  : ee = "B"; break;
      case 6  : ee = "C"; break;
      case 7  : ee = "N"; break;
      case 8  : ee = "O"; break;
      case 9  : ee = "F"; break;
      case 10 : ee = "Ne"; break;
      default : ee = "Ne"; break;
    }
    if (ee != "?") {
      new Atom(ee).drawElectrons(ctx, x, y, scale);
    } else {
      this.drawShell(ctx, x, y, scale, 0);
    }
  },

  drawNucleus : function(ctx, x, y, scale, options) {
    var aw = this.protons + this.neutrons; // atomic weight
    if (aw <= aw2nc.length) {
      var pc = aw2nc[aw];
      for (var i = 0; 2 * i + 1 < pc.length; i++) {
        var xo = pc[2 * i] * scale;
        var yo = pc[2 * i + 1] * scale;
        if (i % 2 == 0 && i < this.neutrons * 2 || i > this.protons * 2) {
          this.drawNeutron(ctx, x + xo, y + yo, scale)
        } else {
          this.drawProton(ctx, x + xo, y + yo, scale);
        }
      }
      return;
    }
    var ar = 0.08 * Math.pow(aw, 0.333); // atomic radius
    var a = 0;
    for (var j = 0; j < aw; j++) {
      var p = Math.PI * j / (2 * aw) ; // polar angle
      var r = (0.08 + ar * Math.cos(p)) * scale; // distance from origin in 2 dimensions
      a += 0.068 * scale * Math.PI / r; // azimuthal angle
      var xi = r * Math.sin(a);
      var yi = r * Math.cos(a);
      if (j % 2 == 0 && j < this.neutrons * 2) {
        this.drawNeutron(ctx, x + xi, y + yi, scale);
      } else {
        this.drawProton(ctx, x + xi, y + yi, scale);
      }
    }
  },

  drawElectrons : function(ctx, x, y, scale, options) {
    var electrons = this.protons - (options.charge ? options.charge : 0);
    if (electrons < 3) {
      this.drawShell(ctx, x, y, scale, options, 1);
    } else if (electrons >= 3) {
      for (var i = s2e[this.symbol].period; i > 0 ; i--) {
        this.drawShell(ctx, x, y, scale, options, i);
      }
    }
  },

  drawShell : function(ctx, x, y, scale, options, period) {
    if (!period) { period = 1; }
    var r2 = 0.5 * ((period == 0 ? 1 : period) + 0.6) * scale;
    var gradient = ctx.createRadialGradient(x, y, 0, x, y, r2);
    gradient.addColorStop(0, "rgba(255, 255, 255, 1)");
    gradient.addColorStop(0.28, "rgba(255, 255, 255, 0.9)");
    gradient.addColorStop(0.56  , "rgba(255, 255, 255, 0.8)");
    gradient.addColorStop(1, "rgba(245, 245, 200, 0.7)");
    ctx.fillStyle = gradient;
    ctx.fillCircle(x, y, r2);
    if (this.protons < ec.length) {
      var charge = options && options.charge ? options.charge : 0;
      var electrons = this.protons - charge;
      for (var i = 0; i < electrons; i++) {
        this.drawElectron(ctx, x + ec[2 * i] * scale, y + ec[2 * i + 1] * scale, scale);
      }
    }
  },

  drawProton : function(ctx, x, y, scale, options) {
    var gradient = ctx.createRadialGradient(x - 0.04 * scale, y - 0.04 * scale, 0.04 * scale, x, y, 0.32 * scale);
    gradient.addColorStop(0, "#9ECEFF");
    gradient.addColorStop(1, "#404040");
    ctx.fillStyle = gradient;
    ctx.fillCircle(x, y, 0.14 * scale);
    ctx.fillStyle = "#606060";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.font = Math.round(0.22 * scale) + "px sans-serif";
    ctx.fillText("p", x + 0.00 * scale, y - 0.02 * scale, 0.1 * scale);
    ctx.font = "bold " + Math.round(0.12 * scale) + "px sans-serif";
    ctx.fillText("+", x + 0.08 * scale, y - 0.08 * scale, 0.1 * scale);
  },

  drawNeutron : function(ctx, x, y, scale, options) {
    var gradient = ctx.createRadialGradient(x - 0.04 * scale, y - 0.04 * scale, 0.04 * scale, x, y, 0.32 * scale);
    gradient.addColorStop(0, "#E0E0E0");
    gradient.addColorStop(1, "#404040");
    ctx.fillStyle = gradient;
    ctx.fillCircle(x, y, 0.14 * scale);
    ctx.fillStyle = "#606060";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.font = Math.round(0.22 * scale) + "px sans-serif";
    ctx.fillText("n", x + 0.01 * scale, y - 0.02 * scale, 0.1 * scale);
  },

  drawElectron : function(ctx, x, y, scale) {
    var gradient = ctx.createRadialGradient(x - 0.04 * scale, y - 0.04 * scale, 0.04 * scale, x, y, 0.16 * scale);
    gradient.addColorStop(0, "#FFAECE");
    gradient.addColorStop(1, "#404040");
    ctx.fillStyle = gradient;
    ctx.fillCircle(x, y, 0.08 * scale);
    ctx.fillStyle = "#606060";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.font = Math.round(0.18 * scale) + "px sans-serif";
    ctx.fillText("e", x + 0.0 * scale, y - 0.02 * scale, 0.1 * scale);
    ctx.fillText("-", x + 0.1 * scale, y - 0.08 * scale, 0.1 * scale);
  },

  drawHorizontalLegend : function(ctx, x, y, scale) {
    this.drawProton(ctx, x - 0.1 * scale, 80, scale);
    this.drawNeutron(ctx, x - 0.1 * scale, 120, scale);
    this.drawElectron(ctx, x - 0.1 * scale, 160, scale);
    ctx.drawLeftText("Proton", x + 0.1 * scale, 80, scale);
    ctx.drawLeftText("Neutron", x + 0.1 * scale, 120, scale);
    ctx.drawLeftText("Electron", x + 0.1 * scale, 160, scale);
  },

  drawVerticalLegend : function(ctx, x, y, scale) {
    ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
    ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    this.drawProton(ctx, x - 0.48 * scale, y - 0.5 * scale, scale);
    this.drawNeutron(ctx, x - 0.48 * scale, y, scale);
    this.drawElectron(ctx, x - 0.48 * scale, y + 0.5 * scale, scale);
    ctx.font = Math.round(0.25 * scale) + "px sans-serif";
    ctx.drawLeftText("proton", x - 0.24 * scale, y - 0.5 * scale, scale);
    ctx.drawLeftText("neutron", x - 0.24 * scale, y, scale);
    ctx.drawLeftText("electron", x - 0.24 * scale, y + 0.5 * scale, scale);
  }
};

var i2a = function(id) {
  var symbol, weight;
  var ix = id.indexOf('-');
  if (ix > 0) {
    symbol = id.substring(0, ix);
    weight = id.substring(ix + 1);
  } else {
    symbol = id;
    weight = 0;
  }
  var a = new Atom(symbol);
  a.neutrons = weight - a.protons;
  a.mass = a.protons + a.neutrons;
  return a;
};

var i2n = {
  "H-1" : "Hydrogen-1",
  "H-2" : "Hydrogen-2",
  "H-3" : "Hydrogen-3",
  "He-3" : "Helium-3",
  "He-4" : "Helium-4",
  "He-5" : "Helium-5",
  "He-6" : "Helium-6",
  "Li-4" : "Lithium-4",
  "Li-5" : "Lithium-5",
  "Li-6" : "Lithium-6",
  "Li-7" : "Lithium-7",
  "Be-8" : "Beryllium-8",
  "Be-9" : "Beryllium-9",
  "B-10" : "Boron-10",
  "B-11" : "Boron-11",
  "C-12" : "Carbon-12"
};
